iT邦幫忙

2021 iThome 鐵人賽

DAY 8
2
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 8 篇

不只懂 Vue 語法:請說明 style 裏的 scoped、deep selector 的作用?

  • 分享至 

  • xImage
  •  

問題回答

scoped 屬性的作用是避免父元件的 CSS 樣式會污染到子元件的 CSS 樣式。Deep selector 的作用是相反,即使在父元件設定了 scoped css,仍然容許讓父元件的 CSS 樣式穿透到子元件的 CSS 樣式。

以下會詳細討論答案的內容。

CSS scoped 是什麼?

在 .vue 檔案裏,在 style 裏加上 scoped 屬性的作用是避免目前元件的 style 會污染到子元件的 style。開發時,大多情況都建議加上 scoped。

scoped 的效果會經由 vue-loader 來處理。vue-loader 是一個 Webpack 的 loader,它負責解析文件,如有需要會再引用其他 loader 來解析文件內容。最後把所有解析好的資源輸出為一個 ES Module,並預設以物件型別滙出。

回到重點,要實現 scoped,我們只需在 style 裏加上 scoped:

<style lang="scss" scoped>
</style>

舉例說,現在有一個 Home 元件,裏面再包一個 Message 元件。

我們在 Home 元件裏的 style 使用 scoped,並且設定 span 的顏色為紅色。結果是只在 Home 裏的 span 會變成紅色。

Home:

<template>
    <div>
        <span> 首頁的文字 </span>
        <Message />
    </div>
</template>

<script>
    import Message from "@/components/Message";

    export default {
      components: {
        Message,
      },
    };
</script>


<style lang="scss" scoped>
    span {
      color: red;
    }
</style>

Message:

<template>
  <div>
    <span>
      Message 外層文字
      <span> Message 內層文字 </span>
    </span>
  </div>
</template>

<script>
export default {};
</script>

<style lang="scss">
</style>

結果:

看看目前的 HTML 結構:

Vue loader 編釋後,加上 data attribute 來指定只有 Home 元件才會套上 color: red; 這個樣式。因此只有「首頁的文字」才會變成紅色。

即使父元件加了scoped,子元件的根部也會受影響?

在父元件加上 scoped,看似就不會污染到子元件。但其實會污染到子元件的根部。

重用以上例子,如果把 Message 裏最外層的 div 刪走,結果是「外層 Message」和「內層 Message」都會受影響。以下示範把 Message 最外層的 div 拿走:

<template>
    <span>
      Message 外層文字
      <span> Message 內層文字 </span>
    </span>
</template>

<script>
export default {};
</script>

<style lang="scss">
</style>

結果:

因為沒有了 div,而 span 是行內元素,所以這三段文字都排在同一行。而重點是,現在 Message 元件裏的所有文字都變成紅色。

HTML 結構:

在前一個示範,本來 data-fae5bece 這個屬性是套用在 div 上,現在因為把 div 刪掉,所以直接套在 span 上,以致整個 Message 元件裏的 span 都是紅色。

這就是官方文件提及的意思:

使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

對於子元件根部的 CSS,父元件 CSS 會優先於子元件的 CSS

重用以上例子,如果在 Message 加上 scoped,並設定 span { color: blue; }。那麼目前 Message 元件就會同時受父元件和它自己的 scoped 影響,換言之,它會有兩個 data attribute。

以下示範在 Message 元件加上它自己的 span 樣式,顏色是藍色。

Message.vue:

<template>
    <span> Message 外層文字
        <span> Message 內層文字 </span>
    </span> 
</template>

<script>
    export default {}
</script>

<style lang="scss" scoped>
    span {
         color: blue;
    }
</style>  

結果:

HTML 結構:

最外層的 span 被加上兩個 data attribute,但 Home 的 data attribute 被劃掉,因此可見父元件的 scoped CSS 是優先於子元件的 scoped CSS。

完整程式碼

https://codesandbox.io/s/scoped-css-lbc9z

什麼是 deep selector?

deep selector 就是做相反的事。意思是無視 scoped 的限制,即使設定了 scoped,還是可以把父元件的樣式穿透到子元件。

deep selector 的寫法有幾種:

.home >>> .message {
    color: red;
}
.home:deep .message {
    color: red;
}
.home /deep/ .message {
    color: red;
}
.home::v-deep .message {
    color: red;
}

另外也可以把前面的父元件省略:

:deep .message {
    color: red;
}
/deep/ .message {
    color: red;
}
::v-deep .message {
    color: red;
}

注意:

  • 如果你是使用 sass 或 scss, >>> 不能被編釋。
  • vue/compiler-sfc 建議使用 :deep 代替 ::v-deep。

修改之前的例子,在 Home 最外層加上 home class,在 Message 的外層加上 message class。最後,在 Home.vue 檔案使用 deep selector:

Home.vue:

<template>
  <div class="home">
    ...
  </div>
</template>

<script>
    ...
</script>

<style scoped lang="scss">

  span {
    color: red;
  }

  .home:deep .message {
    color: red;  
  }
</style>

Message.vue:

<template>
    <div class="message">
        ...
    </div>
</template>

<script>
export default {}
</script>

<style lang="scss" scoped></style>  

結果:

HTML 結構:

因為我的例子是使用 scss 來寫 CSS,所以不能使用 >>> 的寫法。另外,如果我們在子元件設定 span 樣式,像以下做法:

Message.vue:

<template>
    <div class="message">
        <span> Message 外層文字
            <span> Message 內層文字 </span>
        </span>
    </div>
</template>
<script>

export default {
}
</script>
<style lang="scss" scoped>
 span {
     color: blue;
 }
</style>  

結果是子元件的文字會變成藍色,優先於父元件設定的紅色:

HTML 結構:

完整程式碼(子元件裏沒有設定樣式)

https://codesandbox.io/s/scoped-css-deep-selector-k9111?file=/src/components/Message.vue

其他應用情況:修改第三方套件的樣式

當我們以元件的方式來載入第三方套件,並且想修改它的樣式,就有機會要用 deep selector。例如我曾經在專案中以元件方式載入 CKEditor,但找不到在哪裏可以修改 CKEditor 輸入框的高度,於是我使用 deep selector 的方式來處理:

引入 ckeditor 元件來載入 CKEditor:

<ckeditor v-model="editorData" :editor="editor" :config="editorConfig"></ckeditor>

我當時在 style ,針對 CKEditor 裏某個 class 的設定:

:deep .ck-editor__editable {
  height: 400px;
}

總結

  • 大多情況都建議使用 scoped CSS。
  • scoped 屬性的作用是避免父元件的 CSS 樣式會污染到子元件的 CSS 樣式。
  • Vue 是透過 vue loader 來編釋 .vue 檔案,以及透過 data attribute 的方法,實現 scoped CSS。
  • 使用 deep selector 可以把父元件的 CSS 樣式穿透到子元件的 CSS 樣式,即使父元件的 CSS 設定了 scoped。
  • deep selector 的語法有幾種:>>> 、::v-deep (不建議使用)、 :deep 、/deep/。 但sass 或 scss 無法編釋 >>>。

參考資料

/deep/ 是什麼? — 聊聊 Vue 裡的 scoped css
Vue Loader - Deep Selectors


上一篇
不只懂 Vue 語法:什麼是 Virtual DOM?Vue 如何利用 Virtual DOM?
下一篇
不只懂 Vue 語法:為何元件裏的 data 必須是函式?建立 data 時能否使用箭頭函式?
系列文
不只懂 Vue 語法:Vue.js 觀念篇 共 31 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言